package org.testfire.realm.control.auth.user;

import javax.ejb.Stateless;
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.context.FacesContext;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.*;
import org.apache.log4j.Logger;
import org.testfire.realm.control.exception.UserNotFoundException;
import org.testfire.realm.control.exception.UserUpdateException;
import org.testfire.realm.model.entities.User;

/**
 * <p>UserDBAO: </p>
 *
 * <p>
 * This bean provides accessor methods into the entity bean, as well as
 * a method for updating the entity bean.<br>
 *
 * Calls to the updator method must be wrapped in a transaction.
 *
 *  </p>
 *
 * @author Geoff M. Granum
 * @version 1.0
 */

@PersistenceUnit(name = "persistence/LGSRealmDB")
@Stateless
public class UserDBAO
{
    // Inject the lgsrealm entity manager, as defined in the persistence.xml file.
    @PersistenceContext(unitName = "lgsrealm")
    EntityManager em;

    private Logger log;

    /**
     * Default constructor.
     */
    public UserDBAO()
    {
        log = Logger.getLogger( UserDBAO.class );
    }

    /**
     * Return the database access object (DBAO) for our database.
     *
     * @return UserDBAO The database access object giving us access to m_user data.
     */
    public static UserDBAO getInstance()
    {
        FacesContext context = FacesContext.getCurrentInstance();
        UserDBAO dbao;
        if ( context != null )
        {
            ELContext elContext = context.getELContext();
            ValueExpression ve = context.getApplication().
                getExpressionFactory().createValueExpression( elContext, "#{UserDBAO}", Object.class );
            dbao = (UserDBAO)ve.getValue( elContext );
        }
        else
        {
            dbao = getInstance( true );
        }
        return dbao;
    }

    private static UserDBAO getInstance( boolean noContextAvailable )
    {
        // obtain the initial JNDI context
        Context initCtx = null;
        EntityManagerFactory emf = null;

        try
        {
            initCtx = new InitialContext();
            // perform JNDI lookup to obtain entity manager factory
            emf = (EntityManagerFactory)initCtx.lookup( "java:comp/env/persistence/TestFireDB" );
        }
        catch ( NamingException e )
        {
            Logger.getLogger( UserDBAO.class ).error( e );
        }
        // use factory to obtain application-managed entity manager
        UserDBAO dbao = new UserDBAO();
        dbao.em = emf.createEntityManager();
        return dbao;
    }

    /**
     * Get the User entity with the passed username from the database.
     *
     * @param userName The username of the User Entity that is sought.
     * @return A User Entity object,
     * @throws UserNotFoundException if there is no m_user matching the requested username.
     */
    public User getUser( String userName ) throws UserNotFoundException
    {
        try
        {
            Query q = em.createQuery( "SELECT count(u) FROM User u WHERE u.name = :name" );
            q.setParameter( "name", userName );
            Long count = (Long)q.getSingleResult();
            if(count > 0){
                q = em.createQuery( "SELECT u FROM User u WHERE u.name = :name" );
                q.setParameter( "name", userName );
                return (User)q.getSingleResult();
            }
            else{
                throw new UserNotFoundException("No user with that username exists.");
            }
        }
        catch ( EntityNotFoundException ex )
        {
            log.warn( "No result for userName: " + userName, ex );
            throw new UserNotFoundException( ex );
        }
        catch ( NoResultException ex )
        {
            log.warn( "No result for userName: " + userName, ex );
            throw new UserNotFoundException( ex );
        }
    }

    /**
     * Get the User entity with the passed username from the database.
     *
     * @param id The id of the User Entity that is sought.
     * @return A User Entity object,
     * @throws UserNotFoundException if there is no m_user matching the requested username.
     */
    public User getUser( Integer id ) throws UserNotFoundException
    {
        try
        {
            return em.find( User.class, id );
        }
        catch ( EntityNotFoundException ex )
        {
            log.error( ex );
            throw new UserNotFoundException( ex );
        }
        catch ( NoResultException ex )
        {
            log.error( ex );
            throw new UserNotFoundException( ex );
        }
    }

    public User getCurrentUser() throws UserNotFoundException
    {
        String username = FacesContext.getCurrentInstance().getExternalContext().getRemoteUser();
        return getUser( username );
    }

    /**
     * Get a new User entity, one which is managed by the Application Context.
     *
     * @return A new m_user which is managed by the Application Context.
     * @throws Exception If there has been an error preventing the creation of a managed User Entity.
     */
    public User getNewUser() throws Exception
    {
        try
        {
            // let the server create the object, so that it may handle it.
            FacesContext context = FacesContext.getCurrentInstance();
            ELContext elContext = context.getELContext();
            // If we let the FacesContext create the m_user, then it will become managed automatically
            ValueExpression ve = context.getApplication().
                getExpressionFactory().
                createValueExpression( elContext, "#{User}", Object.class );
            return (User)ve.getValue( elContext );
        }
        catch ( Exception ex )
        {
            Logger.getLogger( UserDBAO.class ).error( ex );
            throw ex;
        }
    }

    /**
     * Check the databse to see if a m_user with <code>userName</code> exists;
     * only an admin level m_user is allowed to check this directly.
     *
     * @param userName The m_user name to test for existence.
     * @return True if the userName is already present in the database.
     */
    public boolean userExists( String userName )
    {
        Logger log = Logger.getLogger( this.getClass() );
        log.info( "UserDBAO.userExists" );
        Query q = em.createQuery( "SELECT COUNT(u.name) from User u WHERE u.name = ?1" );
        q.setParameter( 1, userName );
        Long r = (Long)q.getSingleResult();
        log.info( "UserExists query on '" + userName + "' result: " + r );
        return r != null && r != 0;
    }

    /**
     * Merges back any changes to this object.
     *
     * @param obj User
     * @throws UserUpdateException if the User could not be updated, perhaps due to
     *                             database connection issues or a null entity manager
     *                             (see AbstractUserBean#updateUserEntity for more on this particular error).
     */
    public void update( User obj ) throws UserUpdateException
    {
        log.info( "Updating user: " + obj.getName() );
        try
        {
            em.merge( obj );
        }
        catch ( Exception ex )
        {
            log.error( "Could not merge User: " + obj, ex );
            throw new UserUpdateException( "Could not merge User: " + obj + ex.getMessage() );
        }
    }


}
